package ru.pb.game.network.threading;

import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;

import ru.pb.game.configs.GameThreadingConfig;
import ru.pb.game.network.clients.GameClient;
import ru.pb.game.network.engine.auth.packets.AuthServerToGameServerPacket;
import ru.pb.global.threading.IThreadExecute;
import ru.pb.global.threading.PriorityThreadFactory;

public class ThreadPoolManager implements IThreadExecute
{
	private static final Logger _log = Logger.getLogger(ThreadPoolManager.class);

	private static ThreadPoolManager _instance;

	private ScheduledThreadPoolExecutor _scheduledThreadPool;
	private final PacketRunner _packetRunner;

	private boolean _shutdown;

	public static ThreadPoolManager getInstance()
	{
		if(_instance == null)
		{
			_instance = new ThreadPoolManager();
		}
		return _instance;
	}

	private ThreadPoolManager()
	{
		_scheduledThreadPool = new ScheduledThreadPoolExecutor(GameThreadingConfig.THREAD_POOL_SIZE, new PriorityThreadFactory("GerenalThreadPool", Thread.NORM_PRIORITY));

		switch(GameThreadingConfig.THREAD_MODEL)
		{
			case 1:
				_packetRunner = new SingleThreadedPacketRunner();
				break;
			case 2:
				_packetRunner = new AsynchronousPacketRunner();
				break;
			default:
				_packetRunner = new ContextPacketRunner();
				break;
		}
	}

	@SuppressWarnings("unchecked")
	public <T extends Runnable>ScheduledFuture<T> scheduleAtFixedRate(T r, long initial, long delay)
	{
		try
		{
			// log(r.getClass(), "schedule");
			return (ScheduledFuture<T>) _scheduledThreadPool.scheduleAtFixedRate(r, correctTime(initial), correctTime(delay), TimeUnit.MILLISECONDS);
		}
		catch(RejectedExecutionException e)
		{
			if( !isShutdown())
			{
				_log.info("Exception: " + e, e);
			}
			return null;
		}
	}

	@Override
	public ScheduledFuture<?> schedule(Runnable r, long delay)
	{
		try
		{
			// log(r.getClass(), "schedule");
			return _scheduledThreadPool.schedule(r, correctTime(delay), TimeUnit.MILLISECONDS);
		}
		catch(RejectedExecutionException e)
		{
			if( !isShutdown())
			{
				_log.info("Exception: " + e, e);
			}
			return null;
		}
	}

	@Override
	public void execute(Runnable r)
	{
		try
		{
			// log(r.getClass(), "execute");
			_scheduledThreadPool.execute(r);
		}
		catch(Exception e)
		{
			if( !isShutdown())
			{
				_log.info("Exception: " + e, e);
			}
		}
	}

	public void executeGameClientPacket(AuthServerToGameServerPacket packet)
	{
		_packetRunner.runPacket(packet);
	}

	private long correctTime(long g)
	{
		if(g <= 0)
			g = 1;

		return g;
	}

	public void onClientDisconnection(GameClient client)
	{
		_packetRunner.removeContext(client);
	}

	public String[] getStats()
	{
		return new String[] {"Scheduled Thread Pool:", //
			" |- ActiveThreads:   " + _scheduledThreadPool.getActiveCount(), //
			" |- getCorePoolSize: " + _scheduledThreadPool.getCorePoolSize(), //
			" |- PoolSize:        " + _scheduledThreadPool.getPoolSize(), //
			" |- MaximumPoolSize: " + _scheduledThreadPool.getMaximumPoolSize(), //
			" |- CompletedTasks:  " + _scheduledThreadPool.getCompletedTaskCount(), //
			" |- ScheduledTasks:  " + (_scheduledThreadPool.getTaskCount() - _scheduledThreadPool.getCompletedTaskCount()), //
		};
	}

	public void shutdown()
	{
		_shutdown = true;
		try
		{
			// в обратном порядке шатдауним потоки
			_scheduledThreadPool.awaitTermination(1, TimeUnit.SECONDS);

			_packetRunner.shutdown();
			_scheduledThreadPool.shutdown();
			System.out.println("All ThreadPools are now stoped.");
		}
		catch(InterruptedException e)
		{
			e.printStackTrace();
		}
	}

	private String getThreadPoolStats(ThreadPoolExecutor pool, String poolname)
	{
		ThreadFactory tf = pool.getThreadFactory();
		if( !(tf instanceof PriorityThreadFactory))
		{
			return "This should not be seen, pool " + poolname;
		}

		StringBuilder res = new StringBuilder();
		PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
		int count = ptf.getGroup().activeCount();
		Thread[] threads = new Thread[count + 2];
		ptf.getGroup().enumerate(threads);

		res.append("\r\nThread Pool: ").append(poolname);
		res.append("\r\nTasks in the queue: ").append(pool.getQueue().size());
		res.append("\r\nThreads stack trace:");
		res.append("\r\nThere should be ").append(count).append(" threads\r\n");

		for(Thread t : threads)
		{
			if(t == null)
			{
				continue;
			}
			res.append("\r\n").append(t.getName());
			StackTraceElement[] trace = t.getStackTrace();
			if(trace.length == 0 || trace[0] == null || trace[0].toString().contains("sun.misc.Unsafe.park"))
			{
				continue;
			}
			// Пропускаем пустые
			for(StackTraceElement ste : t.getStackTrace())
			{
				res.append("\r\n").append(ste);
			}
		}

		return res.toString();
	}

	public synchronized void log(Class<?> clazz, String type)
	{
		try
		{
			FileWriter writer = new FileWriter("./pool.txt", true);
			writer.write(type + " " + clazz.getSimpleName() + "\n");
			writer.close();
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
	}

	public boolean isShutdown()
	{
		return _shutdown;
	}

	public ScheduledThreadPoolExecutor getScheduledThreadPool()
	{
		return _scheduledThreadPool;
	}
}